package Gap;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Random;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import com.dukascopy.api.Configurable;
import com.dukascopy.api.IAccount;
import com.dukascopy.api.IBar;
import com.dukascopy.api.IConsole;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IEngine;
import com.dukascopy.api.IHistory;
import com.dukascopy.api.IMessage;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IStrategy;
import com.dukascopy.api.ITick;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.OfferSide;
import com.dukascopy.api.Period;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IOrder.State;

public class Gap_02Tester implements IStrategy{
	
	@Configurable("Main order TP Koef") public int _MainTPKoef = 1;
	@Configurable("Main order SL Koef") public int _MainSLKoef = 3;
	@Configurable("Order in loss zone") public boolean _OrderInLossZone = true;
	@Configurable("Open level in loss zone") public int _OpenLossZoneLevel = 1;
	@Configurable("Loss zone TP Koef") public int _LossZoneTP = 4;
	@Configurable("Loss zone SL Koef") public int _LossZoneSL = 1;
	
	@Configurable("Min GAP size") public int _MinGapSize = 1;
	@Configurable("Max GAP size") public int _MaxGapSize = 500;
	
	@Configurable("High Low filter") public boolean _HiLowFilter = false;
	@Configurable("Fix hi_low value") public int _FixHiLowVal = 5;
	
	@Configurable("Move SL after fix pips") public boolean _MoveSLFixPip = false;
	@Configurable("Fix pip") public int _FixPip = 10;
	
	@Configurable("Reopen") public boolean _ReOPenFlag = false;
	@Configurable("Reopen amount") public int _ReopenAmount = 2;
	
	@Configurable("Order lot") public double _OrderLots = 0.1;
	
	@Configurable("Instrument") public Instrument _GlobalIntsr = Instrument.EURUSD;
																
	
	private IEngine engine;
	private IConsole console;
	private IHistory history;
	private IContext context;
	private IOrder order = null;
	private SimpleDateFormat sdf;
	
	private Calendar calendar = Calendar.getInstance();
	
	private ArrayList<GapElement> iGap = new ArrayList<Gap_02Tester.GapElement>();
	
	private boolean GapSearchFlag = true;
	
	private double lastAskPrice;
	private double lastBidPrice;
	private IBar lastBidBar = null;
	private IBar lastAskBar = null;
	
	@Override
	public void onStart(IContext context) throws JFException {
		// TODO Auto-generated method stub
		this.engine = context.getEngine();
		this.console = context.getConsole();
		this.history = context.getHistory();
		this.context = context;
		
		sdf = new SimpleDateFormat();
    	DateFormat.getDateInstance(DateFormat.LONG);
    	sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
	}

	@Override
	public void onTick(Instrument instrument, ITick tick) throws JFException {
		// TODO Auto-generated method stub
		IBar OpenGapBar = null;
		
		if(!instrument.equals(_GlobalIntsr)){
    		return;	
    	}
		
		if(lastBidBar == null){
			return;
		}
		
		if(CheckDate(lastBidBar.getTime())){
			return;
		}
		
		if((lastBidPrice == tick.getBid()) && (lastAskPrice == tick.getAsk())){
			return;
		}
		
		calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
    	calendar.setTimeInMillis(tick.getTime());
    	boolean _MoveSL_DeleteSLOrders = false;
    	
    	int currentDay = calendar.get(Calendar.DAY_OF_WEEK);
    	int currentHout = calendar.get(Calendar.HOUR_OF_DAY);
		
    	
    	if(iGap.size() != 0){
    		/*Move SL after the price close the gap. Delete limit and stop orders*/
			for (GapElement gap: iGap){
				
				if((gap.CheckGapClose(tick.getBid()) && (gap.getMoveSLToOpenPrice()))){
					console.getOut().println("GapClosed:-----Move SL to open price and delete stop limit orders!");
					_MoveSL_DeleteSLOrders = true;
					gap.setMoveSLToOpenPrice();
				}
				
				/*
				if((_MoveSLFixPip) && (gap.getMoveSLToOpenPrice())){
					if((gap.getMainOrderID() != null) && (gap.getMainOrderID().getState() != State.FILLED) && (gap.getMainOrderID().getProfitLossInPips() > _FixPip)){
						console.getOut().println("_MoveSLFixPip:-----Move SL to open price and delete stop limit orders!");
						_MoveSL_DeleteSLOrders = true;
						gap.setMoveSLToOpenPrice();
					}
				}*/
				
				if(_MoveSL_DeleteSLOrders){
					_MoveSL_DeleteSLOrders = false;
					for (IOrder iorder : engine.getOrders()){
				       	if((iorder.equals(gap.getMainOrderID())) || (iorder.equals(gap.getLZOrderID()))){ 
				       		IOrder.State myStateOrder = iorder.getState();
					       	/*Delete limit orders*/
				       		if(((iorder.getOrderCommand() == OrderCommand.SELLLIMIT) || (iorder.getOrderCommand() == OrderCommand.BUYLIMIT) ||
					       			(iorder.getOrderCommand() == OrderCommand.BUYSTOP) || (iorder.getOrderCommand() == OrderCommand.SELLSTOP)) && (iorder.getState() == State.OPENED)){
					       		iorder.close();
					       		while (!iorder.getState().equals(IOrder.State.CANCELED)){
			       		            iorder.waitForUpdate(1, TimeUnit.SECONDS);
			       		        }
					       	}
					       	if (myStateOrder == IOrder.State.FILLED){
					       		/*Move SL to open price*/
					       		if(((iorder.getOrderCommand() == OrderCommand.BUY) && (iorder.getStopLossPrice() < iorder.getOpenPrice()))
					       			|| ((iorder.getOrderCommand() == OrderCommand.SELL) && (iorder.getStopLossPrice() > iorder.getOpenPrice()))){
				       				iorder.setStopLossPrice(iorder.getOpenPrice());
				       				gap.DisableReopenForCurrentGap();
					       		}	
					       	}
					    }
					}
				}
		
				/*REOPEN*/
				if((gap.getMainOrderID() != null) && (gap.getMainOrderID().getState() == State.CLOSED) && (_ReOPenFlag)){
					if((gap.getReOpenCount() >= 1) && (gap.getReOpenCount() <= _ReopenAmount)){
						if(gap.getGapSize() > 0){
			    			order = engine.submitOrder(getLabel(instrument,"Reopen"), instrument, OrderCommand.BUYSTOP,_OrderLots,gap.getGapOpenPrice(),5,gap.getGapSL(),gap.getGapTarget());
			    			console.getOut().println("Reopen order buy opened. Order open time: " + sdf.format(tick.getTime()));
			    		}
			    		else if(gap.getGapSize() < 0){
			    			order = engine.submitOrder(getLabel(instrument,"Reopen"), instrument, OrderCommand.SELLSTOP,_OrderLots,gap.getGapOpenPrice(),5,gap.getGapSL(),gap.getGapTarget());
			    			console.getOut().println("Reopen order sell opened. Order open time: " + sdf.format(tick.getTime()));
			    		}
						gap.setMainGapOrderID(order);
						gap.setNewReOpen();
					}
				}
				
				/*Delete limit and stop orders in Friday*/ 
		    	if((currentDay == Calendar.FRIDAY) && (currentHout >= 18)){
		    		if((gap.getMainOrderID() == null) || (gap.getMainOrderID().getState() == State.CLOSED)){
		    			gap = null;
		    		}
		    		for (IOrder iorder : engine.getOrders()){
			       		if(((iorder.getOrderCommand() == OrderCommand.SELLLIMIT) || (iorder.getOrderCommand() == OrderCommand.BUYLIMIT) ||
			       			(iorder.getOrderCommand() == OrderCommand.BUYSTOP) || (iorder.getOrderCommand() == OrderCommand.SELLSTOP)) && (iorder.getState() == State.OPENED)){
				       		iorder.close();
				       		while (!iorder.getState().equals(IOrder.State.CANCELED)){
		       		            iorder.waitForUpdate(1, TimeUnit.SECONDS);
				       		}
		   		        }
		    		}
		    	}
			}
		}
    	
    	/*Delete null objects from the list*/
    	for(int i=0;i<iGap.size();i++){
    		if(iGap.get(i) == null){
    			iGap.remove(i);
    		}
    	}
    	
    	
    	lastAskPrice = tick.getAsk();
		lastBidPrice = tick.getBid();
		
    	if(currentDay != Calendar.MONDAY){
    		GapSearchFlag = true;
    	}
    	
    	/*Work on new gap and open positions*/
    	if((currentHout == 3) && (currentDay == Calendar.MONDAY) && (GapSearchFlag)){
    		for(int j=40; j < 60; j++){
    			calendar .setTimeInMillis(history.getBar(instrument, Period.ONE_HOUR, OfferSide.BID, j).getTime());
    	    	currentDay = calendar.get(Calendar.DAY_OF_WEEK);
    	    	currentHout = calendar.get(Calendar.HOUR_OF_DAY);
    			if((currentDay == Calendar.FRIDAY) && (currentHout == 20)){
					OpenGapBar = history.getBar(instrument, Period.ONE_HOUR, OfferSide.BID, j);
    				break;
    			}
    		}
    		
    		/*Saving new object to List of objects Gap*/
    		iGap.add(new GapElement(OpenGapBar , lastBidBar, instrument,_MainTPKoef,_MainSLKoef));
    		GapSearchFlag = false;
    		
    		console.getOut().println("Curbar =" + lastBidBar.getOpen() + "	Time=" + sdf.format(lastBidBar.getTime()));
        	console.getOut().println("Lastbar =" + OpenGapBar.getClose() + "	Time=" + sdf.format(OpenGapBar.getTime()));
    		
        	console.getOut().println("==========GAP Detected! Size " + iGap.get(iGap.size()-1).GapSize + "================GapTime: " + sdf.format(iGap.get(iGap.size()-1).getGapOpenTime())
    					+ "	Target = " + iGap.get(iGap.size()-1).getGapCloseLevel());
    		
        	if((Math.abs(iGap.get(iGap.size()-1).getGapSize()) < _MinGapSize) || (Math.abs(iGap.get(iGap.size()-1).getGapSize()) > _MaxGapSize)){
    			console.getOut().println("Min_Max filter: gapsize value out of range! (" + _MinGapSize + ";" + _MaxGapSize + ")");
    			return;
    		}
    		
    		if(_HiLowFilter){
    			if(iGap.get(iGap.size()-1).getHiLowDelta() < _FixHiLowVal){
    				console.getOut().println("HiLowFiler: gapsize out of range! HiLowDelta = " + iGap.get(iGap.size()-1).getHiLowDelta());
    				return;
    			}
    		}
    		
    		iGap.get(iGap.size()-1).setNewReOpen();
    		
    		/*Opening main positions*/
    		if(iGap.get(iGap.size()-1).getGapSize() > 0){
    			order = engine.submitOrder(getLabel(instrument,"Main"), instrument, OrderCommand.BUY,_OrderLots,tick.getAsk(),5,iGap.get(iGap.size()-1).getGapSL(),iGap.get(iGap.size()-1).getGapTarget());
    			console.getOut().println("Main order buy opened. Order open time: " + sdf.format(tick.getTime()));
    		}
    		else if(iGap.get(iGap.size()-1).getGapSize() < 0){
    			order = engine.submitOrder(getLabel(instrument,"Main"), instrument, OrderCommand.SELL,_OrderLots,tick.getBid(),5,iGap.get(iGap.size()-1).getGapSL(),iGap.get(iGap.size()-1).getGapTarget());
    			console.getOut().println("Main order sell opened. Order open time: " + sdf.format(tick.getTime()));
    		}
    		iGap.get(iGap.size()-1).setMainGapOrderID(order);
    		
    		/*Opening positions in loss zone*/
    		if(_OrderInLossZone){
    			double openprice = iGap.get(iGap.size()-1).getGapOpenPrice() - _OpenLossZoneLevel*iGap.get(iGap.size()-1).getGapSize()*instrument.getPipValue();
				double sl = openprice - iGap.get(iGap.size()-1).getGapSize()*_LossZoneSL*instrument.getPipValue();
				double tp = openprice + iGap.get(iGap.size()-1).getGapSize()*_LossZoneTP*instrument.getPipValue();
    			if(iGap.get(iGap.size()-1).getGapSize() > 0){
    				order = engine.submitOrder(getLabel(instrument,"LZO"), instrument, OrderCommand.BUYLIMIT,_OrderLots,openprice,5,sl,tp);
    				console.getOut().println("LOSS ZONE order buylimit opened. Order open price: " + openprice);
        		}
        		else if(iGap.get(iGap.size()-1).getGapSize() < 0){
        			order = engine.submitOrder(getLabel(instrument,"LZO"), instrument, OrderCommand.SELLLIMIT,_OrderLots,openprice,5,sl,tp);
        			console.getOut().println("LOSS ZONE order selllimit opened. Order open price: " + openprice);
        		}
    			iGap.get(iGap.size()-1).setLZGapOrderID(order);
    		}
    	}
		
	}

	@Override
	public void onBar(Instrument instrument, Period period, IBar askBar,
			IBar bidBar) throws JFException {
		// TODO Auto-generated method stub
		if(!instrument.equals(_GlobalIntsr) || !period.equals(Period.ONE_MIN)) {
    		return;
    	}
		
		lastBidBar = bidBar;
		lastAskBar = askBar;
	}

	@Override
	public void onMessage(IMessage message) throws JFException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onAccount(IAccount account) throws JFException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onStop() throws JFException {
		// TODO Auto-generated method stub
		
	}
protected String getLabel(Instrument instrument, String com) {
    	
        String label = instrument.name();
       
        Random generator = new Random();
        int r = generator.nextInt(99999);

        label = label.substring(0, 2) + label.substring(3, 5);
        
        label = label + r + com;
        label = label.toLowerCase();
        return label;
    }
	
    protected Boolean CheckDate(long time){
    	
    	Calendar calendar = Calendar.getInstance();
    	calendar .setTimeZone(TimeZone.getTimeZone("GMT"));
    	calendar .setTimeInMillis(time);
    	
    	int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
    	int currentDay = calendar.get(Calendar.DAY_OF_WEEK);
    	
    	if(((currentDay == Calendar.FRIDAY) && (currentHour > 21)) || (currentDay == Calendar.SATURDAY) 
    			|| ((currentDay == Calendar.SUNDAY) && (currentHour < 22))){
    		return true;
    	}
    	return false;
    }
	
	/**
	 * @author Barbos
	 *Class which contain information about gap. Gap size, open and close price
	 *stop and target for orders
	 *
	 */
	private class GapElement {
		
		public GapElement(IBar lastBar, IBar curBar,Instrument instr, int tpK, int slK){
			instrPipVal = instr.getPipValue();
			double tempSize = (lastBar.getClose() - curBar.getOpen())/instrPipVal;
			BigDecimal bigDecimal = new BigDecimal(tempSize);
			bigDecimal = bigDecimal.setScale(0, BigDecimal.ROUND_UP);
			GapSize = bigDecimal.intValue();
			
			if(GapSize > 0){
				tempSize = (lastBar.getLow() - curBar.getOpen())/instrPipVal;
				bigDecimal = new BigDecimal(tempSize);
				bigDecimal = bigDecimal.setScale(0, BigDecimal.ROUND_UP);
				HiLowDelta = bigDecimal.intValue();
			}
			else if(GapSize < 0){
				tempSize = (curBar.getOpen() - lastBar.getHigh())/instrPipVal;
				bigDecimal = new BigDecimal(tempSize);
				bigDecimal = bigDecimal.setScale(0, BigDecimal.ROUND_UP);
				HiLowDelta = bigDecimal.intValue();
			}
			
			GapMainTarget = lastBar.getClose();
			StopLossKoef = slK;
			TakeProfitKoef = tpK;
			GapOpenPrice = curBar.getOpen();
			GapOpenTime = curBar.getTime();
			ReOpenCount = 0;
		}
		
		void setMainGapOrderID (IOrder order){
			MainOrderID = order;
		}
		
		void setLZGapOrderID (IOrder order){
			LZOrderID = order;
		}
		
		IOrder getMainOrderID() {
			return MainOrderID;
		}
		
		IOrder getLZOrderID() {
			return LZOrderID;
		}
		
		private boolean CheckGapClose(double price){
			if(((GapSize > 0) && (price >= GapMainTarget)) || ((GapSize < 0) && (price <= GapMainTarget))){
				return true;
			}
			return false;
		}
		
		void DisableReopenForCurrentGap(){
			ReOpenCount = 0;
		}
		
		int getReOpenCount(){
			return ReOpenCount;
		}
		
		void setNewReOpen(){
			ReOpenCount++;
		}
		
		double getHiLowDelta(){
			return HiLowDelta;
		}
		
		double getGapCloseLevel(){
			return GapMainTarget;
		}
		
		double getGapSL(){
			double result = 0;
			if(GapSize > 0){
				result = GapOpenPrice - StopLossKoef*GapSize*instrPipVal;
			}
			else if (GapSize < 0){
				result = GapOpenPrice - StopLossKoef*GapSize*instrPipVal;
			}
			return result;
		}
		
		double getGapSize(){
			return GapSize;
		}
		
		double getGapTarget(){
			double result = 0;
			if(GapSize > 0){
				result = GapOpenPrice + TakeProfitKoef*GapSize*instrPipVal;
			}
			else if (GapSize < 0){
				result = GapOpenPrice + TakeProfitKoef*GapSize*instrPipVal;
			}
			return result;
		}
		
		double getGapOpenPrice(){
			return GapOpenPrice;
		}
		
		long getGapOpenTime(){
			return GapOpenTime;
		}
		
		void setMoveSLToOpenPrice(){
			MoveSLToOpenPrice = false;
		}
		
		boolean getMoveSLToOpenPrice(){
			return MoveSLToOpenPrice;
		}
		
		private int GapSize = 0;
		private int StopLossKoef = 0;
		private int TakeProfitKoef = 0;
		private int ReOpenCount = 0;
		
		private double instrPipVal = 0;
		private double GapMainTarget = 0;
		private double GapOpenPrice = 0;
		private double HiLowDelta = 0;
		
		private long GapOpenTime = 0;
		
		private IOrder MainOrderID = null;
		private IOrder LZOrderID = null;
		private boolean MoveSLToOpenPrice = true;
		
	}
}
